ielts-fixation.md
ELTS Fast Application — Full Audit Report
Date: April 5, 2026
Server: 46.62.228.173 (Hetzner, Debian, 16GB RAM)
Domain: ielts.fast
Uptime: 40 days, 12 hours

📸 App Screenshots
Preview unavailable

Preview unavailable

1. Infrastructure & Service Status
Component	Status	Details
Docker webapp_ielts	✅ Healthy	Up 33 hours, port 127.0.0.1:8093→3000
PocketBase	✅ Running	127.0.0.1:8090, process-based (not Docker)
Caddy (Reverse Proxy)	✅ Running	Handles TLS for ielts.fast, pb.ielts.fast
UFW Firewall	✅ Active	Only ports 22, 80, 443 open
HTTPS/TLS	✅ Valid	HTTP/2, automatic via Caddy
Server Load	✅ Low	0.31 / 0.23 / 0.16
2. Frontend Stability Assessment
✅ What's Working Well
All sections load correctly: Vocabulary, Grammar, Speaking, Writing, Reading, Exercises, IELTS Guide, Follow-up, Phonetics
No console errors detected during navigation
No broken UI elements — professional dark theme, responsive layout
Service Worker + PWA support configured
Cache busting properly implemented (index.html & sw.js are no-store, no-cache)
Static asset fingerprinting (hashed filenames like index-BFnh6tpG.js)
Code splitting working (lazy-loaded chunks for each major section)
Premium gating correctly shows lock icons on restricted content
⚠️ Minor Issues
X-Powered-By: Express header is exposed — reveals server technology
No security headers from Express (HSTS, X-Content-Type-Options, etc.) — Caddy partially compensates but not for the ielts.fast block specifically
.DS_Store file deployed in /dist/ directory (macOS artifact, minor info leak)
3. PocketBase Integration Assessment
✅ Working Correctly
Health endpoint: GET /api/health → 200 OK
Authentication flow: Registration → Login → Profile creation all properly chained
Token management: Uses localStorage for persistence (migrated from sessionStorage)
Token refresh: Auth-refresh endpoint used to maintain sessions
Profile fetching: Authenticated users can only see their own profiles
Learning data sync: Data stored per-user with proper auth checks
Premium code redemption: Custom API endpoint at /api/custom/redeem-code
Approval workflow: Custom API at /api/custom/submit-approval
PocketBase hooks: Auto-unlock on approval, user verification ↔ profile approval sync
Database Statistics
Collection	Records
Users	14
Profiles	6
Activation Codes	—
Feature Unlocks	—
Learning Data	—
Approval Requests	—
4. Security Assessment
🟢 Strong Points
Area	Status	Detail
API Rules (users)	✅ Secure	List/View: id = @request.auth.id — users can only see themselves
API Rules (profiles)	✅ Secure	List/View: user = @request.auth.id — only own profile visible
API Rules (learning_data)	✅ Secure	All CRUD restricted to user = @request.auth.id
API Rules (activation_codes)	✅ Secure	Admin-only access on all operations
API Rules (approval_requests)	✅ Secure	Users can only view own requests; only admins can update/delete
API Rules (feature_unlocks)	✅ Secure	Users can only list/view own; create/update/delete: 1 = 2 (impossible)
PocketBase Admin Panel	✅ Protected	Requires superuser authentication
Firewall (UFW)	✅ Active	DROP policy, only SSH/HTTP/HTTPS allowed
CSRF Tokens	✅ Implemented	Frontend sends X-CSRF-Token headers
Rate Limiting	✅ Implemented	Client-side rate limiting on register/login attempts
Input Validation	✅ Good	Field-level validation for name, phone, email, password, etc.
Password Generation	✅ Strong	24-char random passwords for auto-registration
PocketBase on localhost	✅ Correct	Bound to 127.0.0.1:8090 only
DOMPurify	✅ Included	XSS protection library present in dependencies
🔴 Critical Security Issues
CAUTION

1. PocketBase Admin Panel Publicly Accessible via pb.ielts.fast

The pb.ielts.fast subdomain directly exposes the PocketBase admin dashboard (/_/) to the internet with NO additional protection (no IP whitelist, no HTTP Basic Auth). While it requires superuser credentials, it's a brute-force attack target.

CAUTION

2. Port 3002 (Uptime Kuma) Exposed to the Internet

Docker bypasses UFW by default. Uptime Kuma on port 3002 is accessible externally at http://46.62.228.173:3002/. This leaks server monitoring data and is a potential attack vector.

CAUTION

3. Port 8080 (Python HTTP Server) Exposed to the Internet

A Python SimpleHTTPServer is running on 0.0.0.0:8080 — fully accessible externally. This could serve arbitrary files if misconfigured, potentially exposing sensitive server files.

🟡 Medium Security Issues
WARNING

4. Missing Security Headers on ielts.fast Caddy Block

Unlike other sites (promedic1.com, super.promedic1.com), the ielts.fast Caddy block does NOT include security headers:

No Strict-Transport-Security (HSTS)
No X-Content-Type-Options
No X-Frame-Options
No X-XSS-Protection
No Referrer-Policy
No Permissions-Policy
No Content-Security-Policy
X-Powered-By: Express is still exposed
WARNING

5. new_schema Collection Has No API Rules

The new_schema collection exists with no list/view/create/update/delete rules — it defaults to superuser-only (safe by default in PB v0.23+), but it's an abandoned table that should be deleted.

WARNING

6. Admin Role Check is Client-Side Only

The isAdmin() function checks user.admin === true || user.role === 'admin' but the users table has no admin or role column. This means the admin dashboard check may never actually work unless it falls through to a superuser check.

WARNING

7. Open User Registration (By Design, but Risky)

Anyone can create user accounts via POST /api/collections/users/records without authentication. While this is needed for the registration flow, it enables:

Account enumeration via email
Spam account creation (rate limiting is client-side only)
Database bloat from bot registrations
🔵 Low/Informational Issues
NOTE

8. .DS_Store in Production Build

macOS .DS_Store file is deployed in the /dist/ directory. While harmless, it reveals directory structure information.

NOTE

9. PocketBase Not Running as a systemd Service

PocketBase is running as a nohup background process, not a systemd service. This means it may not automatically restart after a server reboot.

NOTE

10. Test/Debug Users in Database

Several test accounts remain in the database (playwright tests, debug tests). These should be cleaned for production.

5. Recommended Fixes (Priority Order)
🔴 Fix Immediately
1. Block Port 3002 and 8080 from External Access

bash
# Option A: Docker firewall rules
iptables -I DOCKER-USER -p tcp --dport 3002 -j DROP
iptables -I DOCKER-USER -p tcp --dport 8080 -j DROP
# Option B: Kill the Python HTTP server if not needed
kill $(lsof -ti:8080)
# Option C: Bind Uptime Kuma to localhost only in its config
2. Restrict pb.ielts.fast Admin Panel

caddy
pb.ielts.fast {
    tls doctor@promedic1.com
    
    # Method 1: IP whitelist (recommended)
    @blocked not remote_ip YOUR_IP_ADDRESS
    respond @blocked 403
    
    # Method 2: Basic auth layer
    basicauth {
        admin $2a$14$HASHED_PASSWORD
    }
    
    reverse_proxy 127.0.0.1:8090 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
    }
}
🟡 Fix Soon
3. Add Security Headers to ielts.fast Caddy Block

caddy
ielts.fast {
    # Add this block:
    header {
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        X-XSS-Protection "1; mode=block"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(self), payment=(), usb=()"
        -Server
        -X-Powered-By
    }
    # ... rest of config
}
4. Add Server-Side Rate Limiting for Registration

Add rate limiting in PocketBase hooks or in Express:

javascript
// In server.js - add rate limiting middleware
import rateLimit from 'express-rate-limit';
const registerLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 5, // limit to 5 registrations per IP
    message: { error: 'Too many registration attempts' }
});
app.use('/api/collections/users/records', registerLimiter);
5. Delete new_schema Collection

bash
sqlite3 /root/ielts-pocketbase/data/data.db "DROP TABLE IF EXISTS new_schema;"
# Also remove from _collections
sqlite3 /root/ielts-pocketbase/data/data.db "DELETE FROM _collections WHERE name = 'new_schema';"
6. Make PocketBase a systemd Service

bash
cat > /etc/systemd/system/ielts-pocketbase.service << 'EOF'
[Unit]
Description=IELTS PocketBase
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/root/ielts-pocketbase
ExecStart=/root/ielts-pocketbase/pocketbase serve --http=127.0.0.1:8090 --dir=/root/ielts-pocketbase/data --hooksDir=/root/ielts-pocketbase/data/pb_hooks
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable ielts-pocketbase
systemctl start ielts-pocketbase
🔵 Optional Improvements
7. Remove .DS_Store from Build

Add to .gitignore and build scripts:

bash
find dist -name '.DS_Store' -delete
8. Clean Test Users

sql
DELETE FROM users WHERE email LIKE '%@example.com' OR email LIKE '%@temp.ielts.fast';
DELETE FROM profiles WHERE user NOT IN (SELECT id FROM users);
6. Overall Verdict
Category	Rating	Notes
Stability	⭐⭐⭐⭐⭐	Excellent — app loads fast, no errors, all sections work
Frontend Quality	⭐⭐⭐⭐⭐	Professional dark UI, code-split, PWA-ready
PocketBase Integration	⭐⭐⭐⭐	Solid — auth flows, hooks, and API rules well implemented
API Security (Rules)	⭐⭐⭐⭐	Good — all collections properly locked down
Infrastructure Security	⭐⭐⭐	Needs attention — exposed ports & admin panel
Security Headers	⭐⭐	Missing on IELTS — unlike other sites on the same server
Process Management	⭐⭐⭐	PocketBase should use systemd for reliability